
/* 
    1.创建对象
        1.工厂模式
        2.构造函数模式 构造函数有prototype原型对象，这个原型对象的constructor属性指向函数本身，函数的原型对象指向新对象的原型对象，构造函数本来就是为了标识对象类型的，使用instanceof来判断原型链是否一致
            2.1在内存中创建了一个新对象
            2.2这个对象的原型对象是构造函数的原型属性
            2.3构造函数中的this被赋值为这个新对象
            2.4执行构造函数的内部的代码
            2.5如果构造函数返回一个非空的对象，那么会覆盖新对象直接返回原有的非空对象，如果没有返回值则返回新对象
        构造函数也是函数，如果直接调用这个函数没有加new，这个函数中的方法和属性会挂载到global或者window对象上，那么这个对象中的this会指向global对象，浏览器时会指向window对象
        3.原型模式
            所有的函数在被创建的时候，都有一个prototype属性，这个属性是个对象，是这个函数的原型对象，那这个原型对象还有个constructor属性，让这个原型对象指向回构造函数
            Person.prototype.constructor = Person
            Person函数new出来的任意对象，p1，p2他们自带的__prototype__原型对象等于Person的prototype属性对象
            Object.getPrototypeOf(p1) == p1.__prototype__ == Person.protoType
            Object.setPrototypeOf(person,{number:2}) 这个setPrototypeof 会重写并继承一个对象的原型关系，第二个参数是新原型对象并继承原来的原型对象的属性
            缺点是会严重影响代码的性能
            通过object.create创建一个新对象并制定新原型，效率会高一些
    2.原型层级
        如果要访问一个对象中的属性，会先根据要找寻的属性名在对象实例的属性中寻找，如果没有找到则回去原型对象中找
        如果对象的一个属性名和其原型对象中的一个属性名相同，那么访问这个属性名的时候会遮盖原型对象中的那个属性
        delete p1.name  意思是删除p1实例对象中的name属性，并不会删除原型对象的name，删除后再调用p1.name是原型的name值
        hasOwnProperty()这个方法是判断属性是在实例对象上还是在原型对象上，如果属性在实例对象上则返回true
        在使用Object.getOwnPropertyDescriptor()读取实例对象的属性描述，只能读取实例的属性描述不会读取实例的原型属性，要原型对象直接调用才能获得其自身的属性描述
    3.原型和in操作符   有两种情况  单独使用和for in循环使用
        （1）单独使用 in 通过对象访问制定属性返回true，无论该属性是在实例上还是原型上
        所以判断某个属性是否在原型上可以通过hasOwnProperty()及in来配合  in返回true  hasOwnProperty返回false 说明这个属性是个原型属性
        hasPrototypeProperty()  如果只有原型对象上有这个属性则返回true，如果实例对象重写了这个属性，遮蔽了原型对象属性，则返回false
        （2）for in 会将对象的实例属性和原型属性中所有可以被枚举的属性都返回回来，被遮蔽的原型属性无法被枚举。
            使用Objet.keys()方法，获得所有对象实例中可枚举的属性
            Object.getOwnPropertyNames()   会列出所有实例的属性，包括不能枚举的
            Object.getOwnPropertySymbols() Es6 新语法，列出所有实例属性的符号
    4.属性枚举的顺序
        for-in循环和Object.keys()的枚举顺序是不确定的，取决于js引擎或者浏览器
        Object.getOwnPropertyNames()、Object.getOwnPropertySymbols()和 Object.assign()的枚举顺序是确定的。先以升序枚举数值键，然后以插入顺序枚举字符串和符号键。
    5.对象迭代
        object.values() 返回对象值的数组 和 object.entries() 返回键值对的数组 ES2017  这俩方法执行对象的浅拷贝
        (1)原型的其他写法见5-1
        (2)原型的动态性 先定义了对象实例，再定义了对象原型对象，实例对象仍然能访问到原型属性，因为是通过指针绑定的
        (3)原生对象原型  Array.prototype String.prototype  可随时新增方法供原生对象使用，但是并不推荐如此使用，推荐是创建一个自定义类继承原生类型
        (4)原型的问题  弱化了向构造函数传递参数的能力，引用类型的共享性问题，如果原型对象中有引用类型属性，那么所有实例的引用类型属性值都是一样的
    
*/
/* 工厂模式 */
/* function  createPerson (name,age,job){
    const o = new Object()
    o.name = name
    o.age = age
    o.job = job
    o.sayName = function(){
        console.log(this.name)
    }
    o.arSayName = ()=>{
        console.log(this.name)
        const fun = ()=>{
            console.log(this.name)
        }
        fun()
    }
    o.reaSayName = function(){
        const fun = ()=>{
            console.log(this.name,'箭头函数中的this只与定义他的那个function中的this有关')
        }
        fun()
    }
    return o
}
const p1 = createPerson('小米',1,2)
const p2 = createPerson('小鹏')
p1.sayName()
p1.arSayName()
p1.reaSayName()
p2.sayName() */

// 构造函数式
/* function Person (name,age,job){
    this.name = name
    this.age = age
    this.job = job
    this.fun =function(){
        console.log(this.name)
    }
}
const p = new Person('小米')
p.fun()
const p2 = new Person('小鹏')
p2.fun()
console.log(p.constructor == Person)
console.log(p2.constructor == Person)
Person('小杜')
global.fun() */

// 原型模式
// prototype
/* function Person() {}
Person.prototype.name = '小米'
Person.prototype.age = 12
Person.prototype.job = 'yu7'
Person.prototype.fun = function(){
    console.log(this.name)
}
const p1 = new Person()
p1.fun() */

// 其他原型写法
/*5-1  function Person() {} 
Person.prototype = {
如果不加这个constructor  那么这个原型对象就不指向Person了 但是仍然不能这么写 这种写法会默认被枚举
// constructor: Person,
name: "Nicholas", 
age: 29, 
job: "Software Engineer", 
sayName() { 
console.log(this.name); 
} 
};  
Object.defineProperty(Person.prototype, "constructor", { 
    enumerable: false, 
    value: Person 
}); 
这么写就不会被枚举了
*/